{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#先引入后面可能用到的包（package）\n",
    "import pandas as pd  \n",
    "from datetime import datetime\n",
    "import backtrader as bt\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline   \n",
    "\n",
    "#正常显示画图时出现的中文和负号\n",
    "from pylab import mpl\n",
    "mpl.rcParams['font.sans-serif']=['SimHei']\n",
    "mpl.rcParams['axes.unicode_minus']=False"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#使用tushare旧版接口获取数据\n",
    "import tushare as ts \n",
    "def get_data(code,autype='qfq',start='2010-01-01',end='2020-03-31'):\n",
    "    df=ts.get_k_data(code,autype='qfq',start=start,end=end)\n",
    "    df.index=pd.to_datetime(df.date)\n",
    "    df['openinterest']=0\n",
    "    df=df[['open','high','low','close','volume','openinterest']]\n",
    "    return df\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在原始的BackTrader中，只允许收取固定比例佣金，所以我们需要扩展其功能。我们首先扩展CommInfoBase类："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# A股手续类，主要包括三项：\n",
    "# 1. 佣金：交易额的0.02%，起征点5元，双向收取;\n",
    "# 2. 过户费：交易额的0.002%，双向收取，忽略仅上海6开头股票收取;\n",
    "# 3. 印花税：交易额的0.1%，卖出方收取;\n",
    "# 其他如经手费和证管费通常都包含在佣金中收取\n",
    "import backtrader as bt\n",
    "\n",
    "class AshareCommInfo(bt.CommInfoBase):\n",
    "    def __init__(self, comm_rate=0.0002, trans_rate=0.00002, stamp_duty_tax=0.001):\n",
    "        super(AshareCommInfo, self).__init__()\n",
    "        self.comm_rate = comm_rate # 佣金\n",
    "        self.trans_rate = trans_rate # 过户费\n",
    "        self.stamp_duty_tax_rate = stamp_duty_tax # 印花税\n",
    "\n",
    "    def _getcommission(self, size, price, pseudoexec):\n",
    "        amount = abs(size) * price\n",
    "        comm_fee = amount * self.comm_rate\n",
    "        if comm_fee < 5.0:#佣金最低5元\n",
    "            comm_fee = 5.0\n",
    "        trans_fee = amount * self.trans_rate\n",
    "        stamp_duty_tax_fee = 0.0\n",
    "        if size < 0:\n",
    "            #卖出\n",
    "            stamp_duty_tax_fee = amount * self.stamp_duty_tax_rate        \n",
    "        return comm_fee + trans_fee + stamp_duty_tax_fee\n",
    "\n",
    "    def getcommission(self, size, price):\n",
    "        '''\n",
    "        计算交易费用方法\n",
    "        '''\n",
    "        return self._getcommission(size, price, pseudoexec=True)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#stampdutycommission.py\n",
    "class stampDutyCommissionScheme(bt.CommInfoBase):\n",
    "    '''本佣金模式下，买入仅支付佣金，卖出支付佣金和印花税'''\n",
    "    params = (\n",
    "        ('stamp_duty',0.001),#印花税，交易额的0.1%，卖出方收取;\n",
    "        ('commission',0.001),#佣金，交易额的0.02%，起征点5元，双向收取;\n",
    "        ('trans_rate',0.0002),#过户费，交易额的0.002%，双向收取，忽略仅上海交易所6开头股票收取;\n",
    "        ('stocklike',True),#\n",
    "        ('commtype',bt.CommInfoBase.COMM_FIXED),#确定数，非百分数\n",
    "        )\n",
    "    def _getcommission(self,size,price,pseudoexec):\n",
    "        '''\n",
    "        if size is greater than 0,this indicates a long / buying of shares.\n",
    "        if size is less than 0,this indicates a short / selling of shares\n",
    "        '''\n",
    "        if size > 0 :#买入\n",
    "            comm_temp = size * price * self.p.commission\n",
    "            return comm_temp if comm_temp > 5 else 5#佣金最低5元\n",
    "        elif size < 0:#卖出\n",
    "            comm_temp =  - size * price * self.p.commission\n",
    "            if comm_temp < 5 :      comm_temp = 5#佣金最低5元#\n",
    "            stamp_temp =  - size * price * self.p.stamp_duty\n",
    "            return comm_temp + stamp_temp\n",
    "\n",
    "        else:\n",
    "            return 0 #just in case for some reaon the size is 0 .\n",
    "    \n",
    "        "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 我们需要修改backtrader/broker.py中的BrokerBase类，添加如下方法：\n",
    "class BrokerBase(with_metaclass(MetaBroker, object)):\n",
    "     def set_commission_obj(self, comm_obj, name=None):\n",
    "        self.comminfo[name] = comm_obj"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "conda info -e"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dataframe=get_data('600000',autype='qfq',start='2015-01-01',end='2020-03-30')#使用tushare获取浦发银行（代码：600000）数据。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class my_strategy1(bt.Strategy):\n",
    "    \n",
    "    #全局设定交易策略的参数\n",
    "    params=(\n",
    "        ('maperiod',21),\n",
    "           )\n",
    " \n",
    "    def __init__(self):\n",
    "        #指定价格序列\n",
    "        self.dataclose=self.datas[0].close\n",
    "        # 初始化交易指令、买卖价格和手续费\n",
    "        self.order = None\n",
    "        self.buyprice = None\n",
    "        self.buycomm = None\n",
    " \n",
    "        #添加移动均线指标，内置了talib模块\n",
    "        self.sma = bt.indicators.SimpleMovingAverage(\n",
    "                      self.dataclose, period=self.params.maperiod)\n",
    "    def next(self):\n",
    "        if self.order: # 检查是否有指令等待执行, \n",
    "            print(self.order)\n",
    "            return\n",
    "        # 检查是否持仓   \n",
    "        if not self.position: # 没有持仓\n",
    "            #执行买入条件判断：收盘价格上涨突破20日均线\n",
    "            if self.dataclose[0] > self.sma[0]and self.dataclose[-1] > self.sma[-1]:\n",
    "                #执行买入\n",
    "                self.order = self.buy(size=100)         \n",
    "                self.log(f\"BUY!!!BUY!!!BUY!!!收盘：{self.dataclose[0]}，资产={round(self.broker.getvalue(),2)}，现金={round(self.broker.getcash(),2)}，买入:{self.order.size}股\")\n",
    "#                 self.cancel(self.order)\n",
    "#                 self.log('sldkfjalsdf涨停无法买入')\n",
    "        else:\n",
    "            #执行卖出条件判断：收盘价格跌破20日均线\n",
    "            if self.dataclose[0] < self.sma[0]  :\n",
    "                #执行卖出\n",
    "                self.order = self.sell(size=100)\n",
    "                self.log(f\"SELL!!SELL!!SELL!!收盘：{self.dataclose[0]}，卖出{self.order.size}股，总资产={round(self.broker.getvalue(),2)}，现金={round(self.broker.getcash(),2)}\")\n",
    "\n",
    "                \n",
    "    #交易记录日志（可省略，默认不输出结果）\n",
    "    def log(self, txt, dt=None,doprint=True):\n",
    "        if doprint:\n",
    "            dt = dt or self.datas[0].datetime.date(0)\n",
    "            print(f'{dt.isoformat()},{txt}')\n",
    "    #记录交易执行情况（可省略，默认不输出结果）\n",
    "    def notify_order(self, order):\n",
    "        # 如果order为submitted/accepted,返回空\n",
    "        if order.status in [order.Submitted, order.Accepted]:\n",
    "#             self.log('order.status in [order.Submitted, order.Accepted]')\n",
    "            if order.isbuy():\n",
    "                self.top_stop = True if self.datas[0].low[0] / self.datas[0].close[-1] > 1.0950 else False\n",
    "                if self.top_stop is True:\n",
    "                    self.cancel(order)\n",
    "                    self.log('涨停无法买入')\n",
    "            if order.issell():\n",
    "                self.botton_stop = True if self.datas[0].high[0] / self.datas[0].close[-1] < 0.910 else False\n",
    "                if self.botton_stop is True:\n",
    "#                     self.reject(order)\n",
    "                    self.log('跌停无法卖出')\n",
    "            return\n",
    "        # 如果order为buy/sell executed,报告价格结果\n",
    "        self.log(f\"open：{self.datas[0].open[0]},high{self.datas[0].high[0]}，low{self.datas[0].low[0]}，close{self.datas[0].close[0]}\")\n",
    "        if order.status in [order.Completed]: \n",
    "            if order.isbuy():\n",
    "                self.log(f'买入价格:{order.executed.price},数量:{order.size}股，成本:{order.executed.value},手续费:{round(order.executed.comm,3)},\\\n",
    "                            出账：{round(abs(order.executed.price*order.size) + order.executed.comm,3)}')\n",
    "                self.buyprice = order.executed.price\n",
    "                self.buycomm = order.executed.comm\n",
    "            else:\n",
    "                self.log(f'卖出价格：{order.executed.price},数量:{order.size}股，成本: {order.executed.value},手续费:{round(order.executed.comm,3)},\\\n",
    "                         入账={round(abs(order.executed.price*order.size) - order.executed.comm,3)}')\n",
    "            self.bar_executed = len(self) \n",
    "        # 如果指令取消/交易失败, 报告结果\n",
    "        elif order.status in [order.Canceled, order.Rejected]:\n",
    "            self.log('交易失败，被取消或被拒绝交易')\n",
    "        elif order.status is  order.Margin:\n",
    "            self.log('交易失败,似乎钱不够了')\n",
    "\n",
    "        self.order = None\n",
    "    #记录交易收益情况（可省略，默认不输出结果）\n",
    "    def notify_trade(self,trade):\n",
    "        if not trade.isclosed:\n",
    "            return\n",
    "        self.log(f'策略收益：毛收益 {trade.pnl:.2f}, 净收益 {trade.pnlcomm:.2f}')\n",
    "    #回测结束后输出结果（可省略，默认输出结果）\n",
    "    def stop(self):\n",
    "        self.log('(MA均线： %2d日) 期末总资产 %.2f' %\n",
    "                 (self.params.maperiod, self.broker.getvalue()), doprint=True)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "\n",
    "#回测期间\n",
    "start=datetime(2018, 3, 31)\n",
    "end=datetime(2018, 9, 30)\n",
    "# 加载数据\n",
    "data = bt.feeds.PandasData(dataname=dataframe,fromdate=start,todate=end)\n",
    "\n",
    "# 初始化cerebro回测系统设置                           \n",
    "cerebro = bt.Cerebro()  \n",
    "#将数据传入回测系统\n",
    "cerebro.adddata(data) \n",
    "# 将交易策略加载到回测系统中\n",
    "cerebro.addstrategy(my_strategy1) \n",
    "# 设置初始资本为10,000\n",
    "startcash = 10000\n",
    "cerebro.broker.setcash(startcash) \n",
    "# 设置交易手续费为 0.2%\n",
    "cerebro.broker.setcommission(commission=0.001,margin=5,automargin=True)\n",
    "# cerebro.broker.set_commission_obj(AshareCommInfo())\n",
    "# cerebro.broker =bt.Cerebro().broker.setcommission(commission=0.001,margin=5,automargin=True)\n",
    "comminfo = stampDutyCommissionScheme(stamp_duty=0.001,commission=0.001)\n",
    "cerebro.broker.addcommissioninfo(comminfo)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "d1=start.strftime('%Y%m%d')\n",
    "d2=end.strftime('%Y%m%d')\n",
    "print(f'初始资金: {startcash} 回测期间：{d1}:{d2}')\n",
    "#运行回测系统\n",
    "cerebro.run()\n",
    "#获取回测结束后的总资金\n",
    "portvalue = cerebro.broker.getvalue()\n",
    "pnl = portvalue - startcash\n",
    "#打印结果\n",
    "print(f'总资产: {round(portvalue,3)}，现金: {round(cerebro.broker.get_cash(),3)}')\n",
    "print(f'净收益: {round(pnl,3)}')\n",
    "cerebro.plot(style='candlestick')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
